<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_31</span></h2>

  <p>ss Info {</p>

  <p>public String id;</p>

  <p>public double data;</p>

  <p>public Info(String name, double data) {</p>

  <p>id = name;</p>

  <p>this.data = data;</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>基本Trash类和sumValue()还是象往常一样。这个类剩下的部分支持原型范式。大家首先会看到两个内部类（被设为static属性，使其成为只为代码组织目的而存在的内部类），它们描述了可能出现的违例。在它后面跟随的是一个Vector trashTypes，用于容纳Class句柄。</p>

  <p>在Trash.factory()中，Info对象id（Info类的另一个版本，与前面讨论的不同）内部的String包含了要创建的那种Trash的类型名称。这个String会与列表中的Class名比较。若存在相符的，那便是要创建的对象。当然，还有很多方法可以决定我们想创建的对象。之所以要采用这种方法，是因为从一个文件读入的信息可以转换成对象。</p>

  <p>发现自己要创建的Trash（垃圾）种类后，接下来就轮到“反射”方法大显身手了。getConstructor()方法需要取得自己的参数――由Class句柄构成的一个数组。这个数组代表着不同的参数，并按它们正确的顺序排列，以便我们查找的构建器使用。在这儿，该数组是用Java 1.1的数组创建语法动态创建的：</p>

  <p>new Class[] {double.class}</p>

  <p>这个代码假定所有Trash类型都有一个需要double数值的构建器（注意double.class与Double.class是不同的）。若考虑一种更灵活的方案，亦可调用getConstructors()，令其返回可用构建器的一个数组。</p>

  <p>从getConstructors()返回的是指向一个Constructor对象的句柄（该对象是java.lang.reflect的一部分）。我们用方法newInstance()动态地调用构建器。该方法需要获取包含了实际参数的一个Object数组。这个数组同样是按Java 1.1的语法创建的：</p>

  <p>new Object[] {new Double(info.data)}</p>

  <p>在这种情况下，double必须置入一个封装（容器）类的内部，使其真正成为这个对象数组的一部分。通过调用newInstance()，会提取出double，但大家可能会觉得稍微有些迷惑――参数既可能是double，也可能是Double，但在调用的时候必须用Double传递。幸运的是，这个问题只存在于基本数据类型中间。</p>

  <p>理解了具体的过程后，再来创建一个新对象，并且只为它提供一个Class句柄，事情就变得非常简单了。就目前的情况来说，内部循环中的return永远不会执行，我们在终点就会退出。在这儿，程序动态装载Class对象，并把它加入trashTypes（垃圾类型）列表，从而试图纠正这个问题。若仍然找不到真正有问题的地方，同时装载又是成功的，那么就重复调用factory方法，重新试一遍。</p>

  <p>正如大家会看到的那样，这种设计方案最大的优点就是不需要改动代码。无论在什么情况下，它都能正常地使用（假定所有Trash子类都包含了一个构建器，用以获取单个double参数）。</p>

  <p>1. Trash子类</p>

  <p>为了与原型机制相适应，对Trash每个新子类唯一的要求就是在其中包含了一个构建器，指示它获取一个double参数。Java 1.1的“反射”机制可负责剩下的所有工作。</p>

  <p>下面是不同类型的Trash，每种类型都有它们自己的文件里，但都属于Trash包的一部分（同样地，为了方便在本章内重复使用）：</p>

  <p>//: Aluminum.java</p>

  <p>// The Aluminum class with prototyping</p>

  <p>package c16.trash;</p>

  <p>public class Aluminum extends Trash {</p>

  <p>private static double val = 1.67f;</p>

  <p>public Aluminum(double wt) { super(wt); }</p>

  <p>public double value() { return val; }</p>

  <p>public static void value(double newVal) {</p>

  <p>val = newVal;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>下面是一种新的Trash类型：</p>

  <p>//: Cardboard.java</p>

  <p>// The Cardboard class with prototyping</p>

  <p>package c16.trash;</p>

  <p>public class Cardboard extends Trash {</p>

  <p>private static double val = 0.23f;</p>

  <p>public Cardboard(double wt) { super(wt); }</p>

  <p>public double value() { return val; }</p>

  <p>public static void value(double newVal) {</p>

  <p>val = newVal;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>可以看出，除构建器以外，这些类根本没有什么特别的地方。</p>

  <p>2. 从外部文件中解析出Trash</p>

  <p>与Trash对象有关的信息将从一个外部文件中读取。针对Trash的每个方面，文件内列出了所有必要的信息――每行都代表一个方面，采用“垃圾（废品）名称:值”的固定格式。例如：</p>

  <p>c16.Trash.Glass:54</p>

  <p>c16.Trash.Paper:22</p>

  <p>c16.Trash.Paper:11</p>

  <p>c16.Trash.Glass:17</p>

  <p>c16.Trash.Aluminum:89</p>

  <p>c16.Trash.Paper:88</p>

  <p>c16.Trash.Aluminum:76</p>

  <p>c16.Trash.Cardboard:96</p>

  <p>c16.Trash.Aluminum:25</p>

  <p>c16.Trash.Aluminum:34</p>

  <p>c16.Trash.Glass:11</p>

  <p>c16.Trash.Glass:68</p>

  <p>c16.Trash.Glass:43</p>

  <p>c16.Trash.Aluminum:27</p>

  <p>c16.Trash.Cardboard:44</p>

  <p>c16.Trash.Aluminum:18</p>

  <p>c16.Trash.Paper:91</p>

  <p>c16.Trash.Glass:63</p>

  <p>c16.Trash.Glass:50</p>

  <p>c16.Trash.Glass:80</p>

  <p>c16.Trash.Aluminum:81</p>

  <p>c16.Trash.Cardboard:12</p>

  <p>c16.Trash.Glass:12</p>

  <p>c16.Trash.Glass:54</p>

  <p>c16.Trash.Aluminum:36</p>

  <p>c16.Trash.Aluminum:93</p>

  <p>c16.Trash.Glass:93</p>

  <p>c16.Trash.Paper:80</p>

  <p>c16.Trash.Glass:36</p>

  <p>c16.Trash.Glass:12</p>

  <p>c16.Trash.Glass:60</p>

  <p>c16.Trash.Paper:66</p>

  <p>c16.Trash.Aluminum:36</p>

  <p>c16.Trash.Cardboard:22</p>

  <p>注意在给定类名的时候，类路径必须包含在内，否则就找不到类。</p>

  <p>为解析它，每一行内容都会读入，并用字串方法indexOf()来建立“:”的一个索引。首先用字串方法substring()取出垃圾的类型名称，接着用一个静态方法Double.valueOf()取得相应的值，并转换成一个double值。trim()方法则用于删除字串两头的多余空格。</p>

  <p>Trash解析器置入单独的文件中，因为本章将不断地用到它。如下所示：</p>

  <p>//: ParseTrash.java</p>

  <p>// Open a file and parse its contents into</p>

  <p>// Trash objects, placing each into a Vector</p>

  <p>package c16.trash;</p>

  <p>import java.util.*;</p>

  <p>import java.io.*;</p>

  <p>public class ParseTrash {</p>

  <p>public static void</p>

  <p>fillBin(String filename, Fillable bin) {</p>

  <p>try {</p>

  <p>BufferedReader data =</p>

  <p>new BufferedReader(</p>

  <p>new FileReader(filename));</p>

  <p>String buf;</p>

  <p>while((buf = data.readLine())!= null) {</p>

  <p>String type = buf.substring(0,</p>

  <p>buf.indexOf(':')).trim();</p>

  <p>double weight = Double.valueOf(</p>

  <p>buf.substring(buf.indexOf(':') + 1)</p>

  <p>.trim()).doubleValue();</p>

  <p>bin.addTrash(</p>

  <p>Trash.factory(</p>

  <p>new Trash.Info(type, weight)));</p>

  <p>}</p>

  <p>data.close();</p>

  <p>} catch(IOException e) {</p>

  <p>e.printStackTrace();</p>

  <p>} catch(Exception e) {</p>

  <p>e.printStackTrace();</p>

  <p>}</p>

  <p>}</p>

  <p>// Special case to handle Vector:</p>

  <p>public static void</p>

  <p>fillBin(String filename, Vector bin) {</p>

  <p>fillBin(filename, new FillableVector(bin));</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在RecycleA.java中，我们用一个Vector容纳Trash对象。然而，亦可考虑采用其他集合类型。为做到这一点，fillBin()的第一个版本将获取指向一个Fillable的句柄。后者是一个接口，用于支持一个名为addTrash()的方法：</p>

  <p>//: Fillable.java</p>

  <p>// Any object that can be filled with Trash</p>

  <p>package c16.trash;</p>

  <p>public interface Fillable {</p>

  <p>void addTrash(Trash t);</p>

  <p>} ///:~</p>

  <p>支持该接口的所有东西都能伴随fillBin使用。当然，Vector并未实现Fillable，所以它不能工作。由于Vector将在大多数例子中应用，所以最好的做法是添加另一个过载的fillBin()方法，令其以一个Vector作为参数。利用一个适配器（Adapter）类，这个Vector可作为一个Fillable对象使用：</p>

  <p>//: FillableVector.java</p>

  <p>// Adapter that makes a Vector Fillable</p>

  <p>package c16.trash;</p>

  <p>import java.util.*;</p>

  <p>public class FillableVector implements Fillable {</p>

  <p>private Vector v;</p>

  <p>public FillableVector(Vector vv) { v = vv; }</p>

  <p>public void addTrash(Trash t) {</p>

  <p>v.addElement(t);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>可以看到，这个类唯一的任务就是负责将Fillable的addTrash()同Vector的addElement()方法连接起来。利用这个类，已过载的fillBin()方法可在ParseTrash.java中伴随一个Vector使用：</p>

  <p>public static void</p>

  <p>fillBin(String filename, Vector bin) {</p>

  <p>fillBin(filename, new FillableVector(bin));</p>

  <p>}</p>

  <p>这种方案适用于任何频繁用到的集合类。除此以外，集合类还可提供它自己的适配器类，并实现Fillable（稍后即可看到，在DynaTrash.java中）。</p>

  <p>3. 原型机制的重复应用</p>

  <p>现在，大家可以看到采用原型技术的、修订过的RecycleA.java版本了：</p>

  <p>//: RecycleAP.java</p>

  <p>// Recycling with RTTI and Prototypes</p>

  <p>package c16.recycleap;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>public class RecycleAP {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector bin = new Vector();</p>

  <p>// Fill up the Trash bin:</p>

  <p>ParseTrash.fillBin("Trash.dat", bin);</p>

  <p>Vector</p>

  <p>glassBin = new Vector(),</p>

  <p>paperBin = new Vector(),</p>

  <p>alBin = new Vector();</p>

  <p>Enumeration sorter = bin.elements();</p>

  <p>// Sort the Trash:</p>

  <p>while(sorter.hasMoreElements()) {</p>

  <p>Object t = sorter.nextElement();</p>

  <p>// RTTI to show class membership:</p>

  <p>if(t instanceof Aluminum)</p>

  <p>alBin.addElement(t);</p>

  <p>if(t instanceof Paper)</p>

  <p>paperBin.addElement(t);</p>

  <p>if(t instanceof Glass)</p>

  <p>glassBin.addElement(t);</p>

  <p>}</p>

  <p>Trash.sumValue(alBin);</p>

  <p>Trash.sumValue(paperBin);</p>

  <p>Trash.sumValue(glassBin);</p>

  <p>Trash.sumValue(bin);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>所有Trash对象――以及ParseTrash及支撑类――现在都成为名为c16.trash的一个包的一部分，所以它们可以简单地导入。</p>

  <p>无论打开包含了Trash描述信息的数据文件，还是对那个文件进行解析，所有涉及到的操作均已封装到static（静态）方法ParseTrash.fillBin()里。所以它现在已经不是我们设计过程中要注意的一个重点。在本章剩余的部分，大家经常都会看到无论添加的是什么类型的新类，ParseTrash.fillBin()都会持续工作，不会发生改变，这无疑是一种优良的设计方案。</p>

  <p>提到对象的创建，这一方案确实已将新类型加入系统所需的变动严格地“本地化”了。但在使用RTTI的过程中，却存在着一个严重的问题，这里已明确地显露出来。程序表面上工作得很好，但却永远侦测到不能“硬纸板”（Cardboard）这种新的废品类型――即使列表里确实有一个硬纸板类型！之所以会出现这种情况，完全是由于使用了RTTI的缘故。RTTI只会查找那些我们告诉它查找的东西。RTTI在这里错误的用法是“系统中的每种类型”都进行了测试，而不是仅测试一种类型或者一个类型子集。正如大家以后会看到的那样，在测试每一种类型时可换用其他方式来运用多形性特征。但假如以这种形式过多地使用RTTI，而且又在自己的系统里添加了一种新类型，很容易就会忘记在程序里作出适当的改动，从而埋下以后难以发现的Bug。因此，在这种情况下避免使用RTTI是很有必要的，这并不仅仅是为了表面好看――也是为了产生更易维护的代码。</p>

  <p>16.5 抽象的应用</p>

  <p>走到这一步，接下来该考虑一下设计方案剩下的部分了――在哪里使用类？既然归类到垃圾箱的办法非常不雅且过于暴露，为什么不隔离那个过程，把它隐藏到一个类里呢？这就是著名的“如果必须做不雅的事情，至少应将其本地化到一个类里”规则。看起来就象下面这样：</p>

  <p>现在，只要一种新类型的Trash加入方法，对TrashSorter对象的初始化就必须变动。可以想象，TrashSorter类看起来应该象下面这个样子：</p>

  <p>class TrashSorter extends Vector {</p>

  <p>void sort(Trash t) { /* ... */ }</p>

  <p>}</p>

  <p>也就是说，TrashSorter是由一系列句柄构成的Vector（系列），而那些句柄指向的又是由Trash句柄构成的Vector；利用addElement()，可以安装新的TrashSorter，如下所示：</p>

  <p>TrashSorter ts = new TrashSorter();</p>

  <p>ts.addElement(new Vector());</p>

  <p>但是现在，sort()却成为一个问题。用静态方式编码的方法如何应付一种新类型加入的事实呢？为解决这个问题，必须从sort()里将类型信息删除，使其需要做的所有事情就是调用一个通用方法，用它照料涉及类型处理的所有细节。这当然是对一个动态绑定方法进行描述的另一种方式。所以sort()会在序列中简单地遍历，并为每个Vector都调用一个动态绑定方法。由于这个方法的任务是收集它感兴趣的垃圾片，所以称之为grab(Trash)。结构现在变成了下面这样：</p>

  <p>其中，TrashSorter需要调用每个grab()方法；然后根据当前Vector容纳的是什么类型，会获得一个不同的结果。也就是说，Vector必须留意自己容纳的类型。解决这个问题的传统方法是创建一个基础“Trash bin”（垃圾筒）类，并为希望容纳的每个不同的类型都继承一个新的衍生类。若Java有一个参数化的类型机制，那就也许是最直接的方法。但对于这种机制应该为我们构建的各个类，我们不应该进行麻烦的手工编码，以后的“观察”方式提供了一种更好的编码方式。</p>

  <p>OOP设计一条基本的准则是“为状态的变化使用数据成员，为行为的变化使用多性形”。对于容纳Paper（纸张）的Vector，以及容纳Glass（玻璃）的Vector，大家最开始或许会认为分别用于它们的grab()方法肯定会产生不同的行为。但具体如何却完全取决于类型，而不是其他什么东西。可将其解释成一种不同的状态，而且由于Java有一个类可表示类型（Class），所以可用它判断特定的Tbin要容纳什么类型的Trash。</p>

  <p>用于Tbin的构建器要求我们为其传递自己选择的一个Class。这样做可告诉Vector它希望容纳的是什么类型。随后，grab()方法用Class BinType和RTTI来检查我们传递给它的Trash对象是否与它希望收集的类型相符。</p>

  <p>下面列出完整的解决方案。设定为注释的编号（如*1*）便于大家对照程序后面列出的说明。</p>

  <p>//: RecycleB.java</p>

  <p>// Adding more objects to the recycling problem</p>

  <p>package c16.recycleb;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>// A vector that admits only the right type:</p>

  <p>class Tbin extends Vector {</p>

  <p>Class binType;</p>

  <p>Tbin(Class binType) {</p>

  <p>this.binType = binType;</p>

  <p>}</p>

  <p>boolean grab(Trash t) {</p>

  <p>// Comparing class types:</p>

  <p>if(t.getClass().equals(binType)) {</p>

  <p>addElement(t);</p>

  <p>return true; // Object grabbed</p>

  <p>}</p>

  <p>return false; // Object not grabbed</p>

  <p>}</p>

  <p>}</p>

  <p>class TbinList extends Vector { //(*1*)</p>

  <p>boolean sort(Trash t) {</p>

  <p>Enumeration e = elements();</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>Tbin bin = (Tbin)e.nextElement();</p>

  <p>if(bin.grab(t)) return true;</p>

  <p>}</p>

  <p>return false; // bin not found for t</p>

  <p>}</p>

  <p>void sortBin(Tbin bin) { // (*2*)</p>

  <p>Enumeration e = bin.elements();</p>

  <p>while(e.hasMoreElements())</p>

  <p>if(!sort((Trash)e.nextElement()))</p>

  <p>System.out.println("Bin not found");</p>

  <p>}</p>

  <p>}</p>

  <p>public class RecycleB {</p>

  <p>static Tbin bin = new Tbin(Trash.class);</p>

  <p>public static void main(String[] args) {</p>

  <p>// Fill up the Trash bin:</p>

  <p>ParseTrash.fillBin("Trash.dat", bin);</p>

  <p>TbinList trashBins = new TbinList();</p>

  <p>trashBins.addElement(</p>

  <p>new Tbin(Aluminum.class));</p>

  <p>trashBins.addElement(</p>

  <p>new Tbin(Paper.class));</p>

  <p>trashBins.addElement(</p>

  <p>new Tbin(Glass.class));</p>

  <p>// add one line here: (*3*)</p>

  <p>trashBins.addElement(</p>

  <p>new Tbin(Cardboard.class));</p>

  <p>trashBins.sortBin(bin); // (*4*)</p>

  <p>Enumeration e = trashBins.elements();</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>Tbin b = (Tbin)e.nextElement();</p>

  <p>Trash.sumValue(b);</p>

  <p>}</p>

  <p>Trash.sumValue(bin);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>(1) TbinList容纳一系列Tbin句柄，所以在查找与我们传递给它的Trash对象相符的情况时，sort()能通过Tbin继承。</p>

  <p>(2) sortBin()允许我们将一个完整的Tbin传递进去，而且它会在Tbin里遍历，挑选出每种Trash，并将其归类到特定的Tbin中。请注意这些代码的通用性：新类型加入时，它本身不需要任何改动。只要新类型加入（或发生其他事件）时大量代码都不需要变化，就表明我们设计的是一个容易扩展的系统。</p>

  <p>(3) 现在可以体会添加新类型有多么容易了。为支持添加，只需要改动几行代码。如确实有必要，甚至可以进一步地改进设计，使更多的代码都保持“固定”。</p>

  <p>(4) 一个方法调用使bin的内容归类到对应的、特定类型的垃圾筒里。</p>

  <p>16.6 多重派遣</p>

  <p>上述设计方案肯定是令人满意的。系统内新类型的加入涉及添加或修改不同的类，但没有必要在系统内对代码作大范围的改动。除此以外，RTTI并不象它在RecycleA.java里那样被不当地使用。然而，我们仍然有可能更深入一步，以最“纯”的角度来看待RTTI，考虑如何在垃圾分类系统中将它完全消灭。</p>

  <p>为达到这个目标，首先必须认识到：对所有与不同类型有特殊关联的活动来说――比如侦测一种垃圾的具体类型，并把它置入适当的垃圾筒里――这些活动都应当通过多形性以及动态绑定加以控制。</p>

  <p>以前的例子都是先按类型排序，再对属于某种特殊类型的一系列元素进行操作。现在一旦需要操作特定的类型，就请先停下来想一想。事实上，多形性（动态绑定的方法调用）整个的宗旨就是帮我们管理与不同类型有特殊关联的信息。既然如此，为什么还要自己去检查类型呢？</p>

  <p>答案在于大家或许不以为然的一个道理：Java只执行单一派遣。也就是说，假如对多个类型未知的对象执行某项操作，Java只会为那些类型中的一种调用动态绑定机制。这当然不能解决问题，所以最后不得不人工判断某些类型，才能有效地产生自己的动态绑定行为。</p>

  <p>为解决这个缺陷，我们需要用到“多重派遣”机制，这意味着需要建立一个配置，使单一方法调用能产生多个动态方法调用，从而在一次处理过程中正确判断出多种类型。为达到这个要求，需要对多个类型结构进行操作：每一次派遣都需要一个类型结构。下面的例子将对两个结构进行操作：现有的Trash系列以及由垃圾筒（Trash Bin）的类型构成的一个系列――不同的垃圾或废品将置入这些筒内。第二个分级结构并非绝对显然的。在这种情况下，我们需要人为地创建它，以执行多重派遣（由于本例只涉及两次派遣，所以称为“双重派遣”）。</p>

  <p>16.6.1 实现双重派遣</p>

  <p>记住多形性只能通过方法调用才能表现出来，所以假如想使双重派遣正确进行，必须执行两个方法调用：在每种结构中都用一个来判断其中的类型。在Trash结构中，将使用一个新的方法调用addToBin()，它采用的参数是由TypeBin构成的一个数组。那个方法将在数组中遍历，尝试将自己加入适当的垃圾筒，这里正是双重派遣发生的地方。</p>

  <p>新建立的分级结构是TypeBin，其中包含了它自己的一个方法，名为add()，而且也应用了多形性。但要注意一个新特点：add()已进行了“过载”处理，可接受不同的垃圾类型作为参数。因此，双重满足机制的一个关键点是它也要涉及到过载。</p>

  <p>程序的重新设计也带来了一个问题：现在的基础类Trash必须包含一个addToBin()方法。为解决这个问题，一个最直接的办法是复制所有代码，并修改基础类。然而，假如没有对源码的控制权，那么还有另一个办法可以考虑：将addToBin()方法置入一个接口内部，保持Trash不变，并继承新的、特殊的类型Aluminum，Paper，Glass以及Cardboard。我们在这里准备采取后一个办法。</p>

  <p>这个设计方案中用到的大多数类都必须设为public（公用）属性，所以它们放置于自己的类内。下面列出接口代码：</p>

  <p>//: TypedBinMember.java</p>

  <p>// An interface for adding the double dispatching</p>

  <p>// method to the trash hierarchy without</p>

  <p>// modifying the original hierarchy.</p>

  <p>package c16.doubledispatch;</p>

  <p>interface TypedBinMember {</p>

  <p>// The new method:</p>

  <p>boolean addToBin(TypedBin[] tb);</p>

  <p>} ///:~</p>

  <p>在Aluminum，Paper，Glass以及Cardboard每个特定的子类型内，都会实现接口TypeBinMember的addToBin()方法，但每种情况下使用的代码“似乎”都是完全一样的：</p>

  <p>//: DDAluminum.java</p>

  <p>// Aluminum for double dispatching</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>public class DDAluminum extends Aluminum</p>

  <p>implements TypedBinMember {</p>

  <p>public DDAluminum(double wt) { super(wt); }</p>

  <p>public boolean addToBin(TypedBin[] tb) {</p>

  <p>for(int i = 0; i &lt; tb.length; i++)</p>

  <p>if(tb[i].add(this))</p>

  <p>return true;</p>

  <p>return false;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: DDPaper.java</p>

  <p>// Paper for double dispatching</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>public class DDPaper extends Paper</p>

  <p>implements TypedBinMember {</p>

  <p>public DDPaper(double wt) { super(wt); }</p>

  <p>public boolean addToBin(TypedBin[] tb) {</p>

  <p>for(int i = 0; i &lt; tb.length; i++)</p>

  <p>if(tb[i].add(this))</p>

  <p>return true;</p>

  <p>return false;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: DDGlass.java</p>

  <p>// Glass for double dispatching</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>public class DDGlass extends Glass</p>

  <p>implements TypedBinMember {</p>

  <p>public DDGlass(double wt) { super(wt); }</p>

  <p>public boolean addToBin(TypedBin[] tb) {</p>

  <p>for(int i = 0; i &lt; tb.length; i++)</p>

  <p>if(tb[i].add(this))</p>

  <p>return true;</p>

  <p>return false;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: DDCardboard.java</p>

  <p>// Cardboard for double dispatching</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>public class DDCardboard extends Cardboard</p>

  <p>implements TypedBinMember {</p>

  <p>public DDCardboard(double wt) { super(wt); }</p>

  <p>public boolean addToBin(TypedBin[] tb) {</p>

  <p>for(int i = 0; i &lt; tb.length; i++)</p>

  <p>if(tb[i].add(this))</p>

  <p>return true;</p>

  <p>return false;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>每个addToBin()内的代码会为数组中的每个TypeBin对象调用add()。但请注意参数：this。对Trash的每个子类来说，this的类型都是不同的，所以不能认为代码“完全”一样――尽管以后在Java里加入参数化类型机制后便可认为一样。这是双重派遣的第一个部分，因为一旦进入这个方法内部，便可知道到底是Aluminum，Paper，还是其他什么垃圾类型。在对add()的调用过程中，这种信息是通过this的类型传递的。编译器会分析出对add()正确的过载版本的调用。但由于tb[i]会产生指向基础类型TypeBin的一个句柄，所以最终会调用一个不同的方法――具体什么方法取决于当前选择的TypeBin的类型。那就是第二次派遣。</p>

  <p>下面是TypeBin的基础类：</p>

  <p>//: TypedBin.java</p>

  <p>// Vector that knows how to grab the right type</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>public abstract class TypedBin {</p>

  <p>Vector v = new Vector();</p>

  <p>protected boolean addIt(Trash t) {</p>

  <p>v.addElement(t);</p>

  <p>return true;</p>

  <p>}</p>

  <p>public Enumeration elements() {</p>

  <p>return v.elements();</p>

  <p>}</p>

  <p>public boolean add(DDAluminum a) {</p>

  <p>return false;</p>

  <p>}</p>

  <p>public boolean add(DDPaper a) {</p>

  <p>return false;</p>

  <p>}</p>

  <p>public boolean add(DDGlass a) {</p>

  <p>return false;</p>

  <p>}</p>

  <p>public boolean add(DDCardboard a) {</p>

  <p>return false;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>可以看到，过载的add()方法全都会返回false。如果未在衍生类里对方法进行过载，它就会一直返回false，而且调用者（目前是addToBin()）会认为当前Trash对象尚未成功加入一个集合，所以会继续查找正确的集合。</p>

  <p>在TypeBin的每一个子类中，都只有一个过载的方法会被过载――具体取决于准备创建的是什么垃圾筒类型。举个例子来说，CardboardBin会过载add(DDCardboard)。过载的方法会将垃圾对象加入它的集合，并返回true。而CardboardBin中剩余的所有add()方法都会继续返回false，因为它们尚未过载。事实上，假如在这里采用了参数化类型机制，Java代码的自动创建就要方便得多（使用C++的“模板”，我们不必费事地为子类编码，或者将addToBin()方法置入Trash里；Java在这方面尚有待改进）。</p>

  <p>由于对这个例子来说，垃圾的类型已经定制并置入一个不同的目录，所以需要用一个不同的垃圾数据文件令其运转起来。下面是一个示范性的DDTrash.dat：</p>

  <p>c16.DoubleDispatch.DDGlass:54</p>

  <p>c16.DoubleDispatch.DDPaper:22</p>

  <p>c16.DoubleDispatch.DDPaper:11</p>

  <p>c16.DoubleDispatch.DDGlass:17</p>

  <p>c16.DoubleDispatch.DDAluminum:89</p>

  <p>c16.DoubleDispatch.DDPaper:88</p>

  <p>c16.DoubleDispatch.DDAluminum:76</p>

  <p>c16.DoubleDispatch.DDCardboard:96</p>

  <p>c16.DoubleDispatch.DDAluminum:25</p>

  <p>c16.DoubleDispatch.DDAluminum:34</p>

  <p>c16.DoubleDispatch.DDGlass:11</p>

  <p>c16.DoubleDispatch.DDGlass:68</p>

  <p>c16.DoubleDispatch.DDGlass:43</p>

  <p>c16.DoubleDispatch.DDAluminum:27</p>

  <p>c16.DoubleDispatch.DDCardboard:44</p>

  <p>c16.DoubleDispatch.DDAluminum:18</p>

  <p>c16.DoubleDispatch.DDPaper:91</p>

  <p>c16.DoubleDispatch.DDGlass:63</p>

  <p>c16.DoubleDispatch.DDGlass:50</p>

  <p>c16.DoubleDispatch.DDGlass:80</p>

  <p>c16.DoubleDispatch.DDAluminum:81</p>

  <p>c16.DoubleDispatch.DDCardboard:12</p>

  <p>c16.DoubleDispatch.DDGlass:12</p>

  <p>c16.DoubleDispatch.DDGlass:54</p>

  <p>c16.DoubleDispatch.DDAluminum:36</p>

  <p>c16.DoubleDispatch.DDAluminum:93</p>

  <p>c16.DoubleDispatch.DDGlass:93</p>

  <p>c16.DoubleDispatch.DDPaper:80</p>

  <p>c16.DoubleDispatch.DDGlass:36</p>

  <p>c16.DoubleDispatch.DDGlass:12</p>

  <p>c16.DoubleDispatch.DDGlass:60</p>

  <p>c16.DoubleDispatch.DDPaper:66</p>

  <p>c16.DoubleDispatch.DDAluminum:36</p>

  <p>c16.DoubleDispatch.DDCardboard:22</p>

  <p>下面列出程序剩余的部分：</p>

  <p>//: DoubleDispatch.java</p>

  <p>// Using multiple dispatching to handle more</p>

  <p>// than one unknown type during a method call.</p>

  <p>package c16.doubledispatch;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>class AluminumBin extends TypedBin {</p>

  <p>public boolean add(DDAluminum a) {</p>

  <p>return addIt(a);</p>

  <p>}</p>

  <p>}</p>

  <p>class PaperBin extends TypedBin {</p>

  <p>public boolean add(DDPaper a) {</p>

  <p>return addIt(a);</p>

  <p>}</p>

  <p>}</p>

  <p>class GlassBin extends TypedBin {</p>

  <p>public boolean add(DDGlass a) {</p>

  <p>return addIt(a);</p>

  <p>}</p>

  <p>}</p>

  <p>class CardboardBin extends TypedBin {</p>

  <p>public boolean add(DDCardboard a) {</p>

  <p>return addIt(a);</p>

  <p>}</p>

  <p>}</p>

  <p>class TrashBinSet {</p>

  <p>private TypedBin[] binSet = {</p>

  <p>new AluminumBin(),</p>

  <p>new PaperBin(),</p>

  <p>new GlassBin(),</p>

  <p>new CardboardBin()</p>

  <p>};</p>

  <p>public void sortIntoBins(Vector bin) {</p>

  <p>Enumeration e = bin.elements();</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>TypedBinMember t =</p>

  <p>(TypedBinMember)e.nextElement();</p>

  <p>if(!t.addToBin(binSet))</p>

  <p>System.err.println("Couldn't add " + t);</p>

  <p>}</p>

  <p>}</p>

  <p>public TypedBin[] binSet() { return binSet; }</p>

  <p>}</p>

  <p>public class DoubleDispatch {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector bin = new Vector();</p>

  <p>TrashBinSet bins = new TrashBinSet();</p>

  <p>// ParseTrash still works, without changes:</p>

  <p>ParseTrash.fillBin("DDTrash.dat", bin);</p>

  <p>// Sort from the master bin into the</p>

  <p>// individually-typed bins:</p>

  <p>bins.sortIntoBins(bin);</p>

  <p>TypedBin[] tb = bins.binSet();</p>

  <p>// Perform sumValue for each bin...</p>

  <p>for(int i = 0; i &lt; tb.length; i++)</p>

  <p>Trash.sumValue(tb[i].v);</p>

  <p>// ... and for the master bin</p>

  <p>Trash.sumValue(bin);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>其中，TrashBinSet封装了各种不同类型的TypeBin，同时还有sortIntoBins()方法。所有双重派遣事件都会在那个方法里发生。可以看到，一旦设置好结构，再归类成各种TypeBin的工作就变得十分简单了。除此以外，两个动态方法调用的效率可能也比其他排序方法高一些。</p>

  <p>注意这个系统的方便性主要体现在main()中，同时还要注意到任何特定的类型信息在main()中都是完全独立的。只与Trash基础类接口通信的其他所有方法都不会受到Trash类中发生的改变的干扰。</p>

  <p>添加新类型需要作出的改动是完全孤立的：我们随同addToBin()方法继承Trash的新类型，然后继承一个新的TypeBin（这实际只是一个副本，可以简单地编辑），最后将一种新类型加入TrashBinSet的集合初化化过程。</p>

  <p>16.7 访问器范式</p>

  <p>接下来，让我们思考如何将具有完全不同目标的一个设计范式应用到垃圾归类系统。</p>

  <p>对这个范式，我们不再关心在系统中加入新型Trash时的优化。事实上，这个范式使新型Trash的添加显得更加复杂。假定我们有一个基本类结构，它是固定不变的；它或许来自另一个开发者或公司，我们无权对那个结构进行任何修改。然而，我们又希望在那个结构里加入新的多形性方法。这意味着我们一般必须在基础类的接口里添加某些东西。因此，我们目前面临的困境是一方面需要向基础类添加方法，另一方面又不能改动基础类。怎样解决这个问题呢？</p>

  <p>“访问器”（Visitor）范式使我们能扩展基本类型的接口，方法是创建类型为Visitor的一个独立的类结构，对以后需对基本类型采取的操作进行虚拟。基本类型的任务就是简单地“接收”访问器，然后调用访问器的动态绑定方法。看起来就象下面这样：</p>

  <p>现在，假如v是一个指向Aluminum（铝制品）的Visitable句柄，那么下述代码：</p>

  <p>PriceVisitor pv = new PriceVisitor();</p>

  <p>v.accept(pv);</p>

  <p>会造成两个多形性方法调用：第一个会选择accept()的Aluminum版本；第二个则在accept()里――用基础类Visitor句柄v动态调用visit()的特定版本时。</p>

  <p>这种配置意味着可采取Visitor的新子类的形式将新的功能添加到系统里，没必要接触Trash结构。这就是“访问器”范式最主要的优点：可为一个类结构添加新的多形性功能，同时不必改动结构――只要安装好了accept()方法。注意这个优点在这儿是有用的，但并不一定是我们在任何情况下的首选方案。所以在最开始的时候，就要判断这到底是不是自己需要的方案。</p>

  <p>现在注意一件没有做成的事情：访问器方案防止了从主控Trash序列向单独类型序列的归类。所以我们可将所有东西都留在单主控序列中，只需用适当的访问器通过那个序列传递，即可达到希望的目标。尽管这似乎并非访问器范式的本意，但确实让我们达到了很希望达到的一个目标（避免使用RTTI）。</p>

  <p>访问器范式中的双生派遣负责同时判断Trash以及Visitor的类型。在下面的例子中，大家可看到Visitor的两种实现方式：PriceVisitor用于判断总计及价格，而WeightVisitor用于跟踪重量。</p>

  <p>可以看到，所有这些都是用回收程序一个新的、改进过的版本实现的。而且和DoubleDispatch.java一样，Trash类被保持孤立，并创建一个新接口来添加accept()方法：</p>

  <p>//: Visitable.java</p>

  <p>// An interface to add visitor functionality to</p>

  <p>// the Trash hierarchy without modifying the</p>

  <p>// base class.</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>interface Visitable {</p>

  <p>// The new method:</p>

  <p>void accept(Visitor v);</p>

  <p>} ///:~</p>

  <p>Aluminum，Paper，Glass以及Cardboard的子类型实现了accept()方法：</p>

  <p>//: VAluminum.java</p>

  <p>// Aluminum for the visitor pattern</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>public class VAluminum extends Aluminum</p>

  <p>implements Visitable {</p>

  <p>public VAluminum(double wt) { super(wt); }</p>

  <p>public void accept(Visitor v) {</p>

  <p>v.visit(this);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: VPaper.java</p>

  <p>// Paper for the visitor pattern</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>public class VPaper extends Paper</p>

  <p>implements Visitable {</p>

  <p>public VPaper(double wt) { super(wt); }</p>

  <p>public void accept(Visitor v) {</p>

  <p>v.visit(this);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: VGlass.java</p>

  <p>// Glass for the visitor pattern</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>public class VGlass extends Glass</p>

  <p>implements Visitable {</p>

  <p>public VGlass(double wt) { super(wt); }</p>

  <p>public void accept(Visitor v) {</p>

  <p>v.visit(this);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>//: VCardboard.java</p>

  <p>// Cardboard for the visitor pattern</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>public class VCardboard extends Cardboard</p>

  <p>implements Visitable {</p>

  <p>public VCardboard(double wt) { super(wt); }</p>

  <p>public void accept(Visitor v) {</p>

  <p>v.visit(this);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>由于Visitor基础类没有什么需要实在的东西，可将其创建成一个接口：</p>

  <p>//: Visitor.java</p>

  <p>// The base interface for visitors</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>interface Visitor {</p>

  <p>void visit(VAluminum a);</p>

  <p>void visit(VPaper p);</p>

  <p>void visit(VGlass g);</p>

  <p>void visit(VCardboard c);</p>

  <p>} ///:~</p>

  <p>c16.TrashVisitor.VGlass:54</p>

  <p>c16.TrashVisitor.VPaper:22</p>

  <p>c16.TrashVisitor.VPaper:11</p>

  <p>c16.TrashVisitor.VGlass:17</p>

  <p>c16.TrashVisitor.VAluminum:89</p>

  <p>c16.TrashVisitor.VPaper:88</p>

  <p>c16.TrashVisitor.VAluminum:76</p>

  <p>c16.TrashVisitor.VCardboard:96</p>

  <p>c16.TrashVisitor.VAluminum:25</p>

  <p>c16.TrashVisitor.VAluminum:34</p>

  <p>c16.TrashVisitor.VGlass:11</p>

  <p>c16.TrashVisitor.VGlass:68</p>

  <p>c16.TrashVisitor.VGlass:43</p>

  <p>c16.TrashVisitor.VAluminum:27</p>

  <p>c16.TrashVisitor.VCardboard:44</p>

  <p>c16.TrashVisitor.VAluminum:18</p>

  <p>c16.TrashVisitor.VPaper:91</p>

  <p>c16.TrashVisitor.VGlass:63</p>

  <p>c16.TrashVisitor.VGlass:50</p>

  <p>c16.TrashVisitor.VGlass:80</p>

  <p>c16.TrashVisitor.VAluminum:81</p>

  <p>c16.TrashVisitor.VCardboard:12</p>

  <p>c16.TrashVisitor.VGlass:12</p>

  <p>c16.TrashVisitor.VGlass:54</p>

  <p>c16.TrashVisitor.VAluminum:36</p>

  <p>c16.TrashVisitor.VAluminum:93</p>

  <p>c16.TrashVisitor.VGlass:93</p>

  <p>c16.TrashVisitor.VPaper:80</p>

  <p>c16.TrashVisitor.VGlass:36</p>

  <p>c16.TrashVisitor.VGlass:12</p>

  <p>c16.TrashVisitor.VGlass:60</p>

  <p>c16.TrashVisitor.VPaper:66</p>

  <p>c16.TrashVisitor.VAluminum:36</p>

  <p>c16.TrashVisitor.VCardboard:22</p>

  <p>程序剩余的部分将创建特定的Visitor类型，并通过一个Trash对象列表发送它们：</p>

  <p>//: TrashVisitor.java</p>

  <p>// The "visitor" pattern</p>

  <p>package c16.trashvisitor;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>// Specific group of algorithms packaged</p>

  <p>// in each implementation of Visitor:</p>

  <p>class PriceVisitor implements Visitor {</p>

  <p>private double alSum; // Aluminum</p>

  <p>private double pSum; // Paper</p>

  <p>private double gSum; // Glass</p>

  <p>private double cSum; // Cardboard</p>

  <p>public void visit(VAluminum al) {</p>

  <p>double v = al.weight() * al.value();</p>

  <p>System.out.println(</p>

  <p>"value of Aluminum= " + v);</p>

  <p>alSum += v;</p>

  <p>}</p>

  <p>public void visit(VPaper p) {</p>

  <p>double v = p.weight() * p.value();</p>

  <p>System.out.println(</p>

  <p>"value of Paper= " + v);</p>

  <p>pSum += v;</p>

  <p>}</p>

  <p>public void visit(VGlass g) {</p>

  <p>double v = g.weight() * g.value();</p>

  <p>System.out.println(</p>

  <p>"value of Glass= " + v);</p>

  <p>gSum += v;</p>

  <p>}</p>

  <p>public void visit(VCardboard c) {</p>

  <p>double v = c.weight() * c.value();</p>

  <p>System.out.println(</p>

  <p>"value of Cardboard = " + v);</p>

  <p>cSum += v;</p>

  <p>}</p>

  <p>void total() {</p>

  <p>System.out.println(</p>

  <p>"Total Aluminum: $" + alSum + "\n" +</p>

  <p>"Total Paper: $" + pSum + "\n" +</p>

  <p>"Total Glass: $" + gSum + "\n" +</p>

  <p>"Total Cardboard: $" + cSum);</p>

  <p>}</p>

  <p>}</p>

  <p>class WeightVisitor implements Visitor {</p>

  <p>private double alSum; // Aluminum</p>

  <p>private double pSum; // Paper</p>

  <p>private double gSum; // Glass</p>

  <p>private double cSum; // Cardboard</p>

  <p>public void visit(VAluminum al) {</p>

  <p>alSum += al.weight();</p>

  <p>System.out.println("weight of Aluminum = "</p>

  <p>+ al.weight());</p>

  <p>}</p>

  <p>public void visit(VPaper p) {</p>

  <p>pSum += p.weight();</p>

  <p>System.out.println("weight of Paper = "</p>

  <p>+ p.weight());</p>

  <p>}</p>

  <p>public void visit(VGlass g) {</p>

  <p>gSum += g.weight();</p>

  <p>System.out.println("weight of Glass = "</p>

  <p>+ g.weight());</p>

  <p>}</p>

  <p>public void visit(VCardboard c) {</p>

  <p>cSum += c.weight();</p>

  <p>System.out.println("weight of Cardboard = "</p>

  <p>+ c.weight());</p>

  <p>}</p>

  <p>void total() {</p>

  <p>System.out.println("Total weight Aluminum:"</p>

  <p>+ alSum);</p>

  <p>System.out.println("Total weight Paper:"</p>

  <p>+ pSum);</p>

  <p>System.out.println("Total weight Glass:"</p>

  <p>+ gSum);</p>

  <p>System.out.println("Total weight Cardboard:"</p>

  <p>+ cSum);</p>

  <p>}</p>

  <p>}</p>

  <p>public class TrashVisitor {</p>

  <p>public static void main(String[] args) {</p>

  <p>Vector bin = new Vector();</p>

  <p>// ParseTrash still works, without changes:</p>

  <p>ParseTrash.fillBin("VTrash.dat", bin);</p>

  <p>// You could even iterate through</p>

  <p>// a list of visitors!</p>

  <p>PriceVisitor pv = new PriceVisitor();</p>

  <p>WeightVisitor wv = new WeightVisitor();</p>

  <p>Enumeration it = bin.elements();</p>

  <p>while(it.hasMoreElements()) {</p>

  <p>Visitable v = (Visitable)it.nextElement();</p>

  <p>v.accept(pv);</p>

  <p>v.accept(wv);</p>

  <p>}</p>

  <p>pv.total();</p>

  <p>wv.total();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意main()的形状已再次发生了变化。现在只有一个垃圾（Trash）筒。两个Visitor对象被接收到序列中的每个元素内，它们会完成自己份内的工作。Visitor跟踪它们自己的内部数据，计算出总重和价格。</p>

  <p>最好，将东西从序列中取出的时候，除了不可避免地向Trash造型以外，再没有运行期的类型验证。若在Java里实现了参数化类型，甚至那个造型操作也可以避免。</p>

  <p>对比之前介绍过的双重派遣方案，区分这两种方案的一个办法是：在双重派遣方案中，每个子类创建时只会过载其中的一个过载方法，即add()。而在这里，每个过载的visit()方法都必须在Visitor的每个子类中进行过载。</p>

  <p>1. 更多的结合？</p>

  <p>这里还有其他许多代码，Trash结构和Visitor结构之间存在着明显的“结合”（Coupling）关系。然而，在它们所代表的类集内部，也存在着高度的凝聚力：都只做一件事情（Trash描述垃圾或废品，而Visitor描述对垃圾采取什么行动）。作为一套优秀的设计方案，这无疑是个良好的开端。当然就目前的情况来说，只有在我们添加新的Visitor类型时才能体会到它的好处。但在添加新类型的Trash时，它却显得有些碍手碍脚。</p>

  <p>类与类之间低度的结合与类内高度的凝聚无疑是一个重要的设计目标。但只要稍不留神，就可能妨碍我们得到一个本该更出色的设计。从表面看，有些类不可避免地相互间存在着一些“亲密”关系。这种关系通常是成对发生的，可以叫作“对联”（Couplet）――比如集合和继承器（Enumeration）。前面的Trash-Visitor对似乎也是这样的一种“对联”。</p>

  <p>16.8 RTTI真的有害吗</p>

  <p>本章的各种设计方案都在努力避免使用RTTI，这或许会给大家留下“RTTI有害”的印象（还记得可怜的goto吗，由于给人印象不佳，根本就没有放到Java里来）。但实际情况并非绝对如此。正确地说，应该是RTTI使用不当才“有害”。我们之所以想避免RTTI的使用，是由于它的错误运用会造成扩展性受到损害。而我们事前提出的目标就是能向系统自由加入新类型，同时保证对周围的代码造成尽可能小的影响。由于RTTI常被滥用（让它查找系统中的每一种类型），会造成代码的扩展能力大打折扣――添加一种新类型时，必须找出使用了RTTI的所有代码。即使仅遗漏了其中的一个，也不能从编译器那里得到任何帮助。</p>

  <p>然而，RTTI本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备引入一种新工具，我把它叫作TypeMap。其中包含了一个Hashtable（散列表），其中容纳了多个Vector，但接口非常简单：可以添加（add()）一个新对象，可以获得（get()）一个Vector，其中包含了属于某种特定类型的所有对象。对于这个包含的散列表，它的关键在于对应的Vector里的类型。这种设计方案的优点（根据Larry O'Brien的建议）是在遇到一种新类型的时候，TypeMap会动态加入一种新类型。所以不管什么时候，只要将一种新类型加入系统（即使在运行期间添加），它也会正确无误地得以接受。</p>

  <p>我们的例子同样建立在c16.Trash这个“包”（Package）内的Trash类型结构的基础上（而且那儿使用的Trash.dat文件可以照搬到这里来）。</p>

  <p>//: DynaTrash.java</p>

  <p>// Using a Hashtable of Vectors and RTTI</p>

  <p>// to automatically sort trash into</p>

  <p>// vectors. This solution, despite the</p>

  <p>// use of RTTI, is extensible.</p>

  <p>package c16.dynatrash;</p>

  <p>import c16.trash.*;</p>

  <p>import java.util.*;</p>

  <p>// Generic TypeMap works in any situation:</p>

  <p>class TypeMap {</p>

  <p>private Hashtable t = new Hashtable();</p>

  <p>public void add(Object o) {</p>

  <p>Class type = o.getClass();</p>

  <p>if(t.containsKey(type))</p>

  <p>((Vector)t.get(type)).addElement(o);</p>

  <p>else {</p>

  <p>Vector v = new Vector();</p>

  <p>v.addElement(o);</p>

  <p>t.put(type,v);</p>

  <p>}</p>

  <p>}</p>

  <p>public Vector get(Class type) {</p>

  <p>return (Vector)t.get(type);</p>

  <p>}</p>

  <p>public Enumeration keys() { return t.keys(); }</p>

  <p>// Returns handle to adapter class to allow</p>

  <p>// callbacks from ParseTrash.fillBin():</p>

  <p>public Fillable filler() {</p>

  <p>// Anonymous inner class:</p>

  <p>return new Fillable() {</p>

  <p>public void addTrash(Trash t) { add(t); }</p>

  <p>};</p>

  <p>}</p>

  <p>}</p>

  <p>public class DynaTrash {</p>

  <p>public static void main(String[] args) {</p>

  <p>TypeMap bin = new TypeMap();</p>

  <p>ParseTrash.fillBin("Trash.dat",bin.filler());</p>

  <p>Enumeration keys = bin.keys();</p>

  <p>while(keys.hasMoreElements())</p>

  <p>Trash.sumValue(</p>

  <p>bin.get((Class)keys.nextElement()));</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>尽管功能很强，但对TypeMap的定义是非常简单的。它只是包含了一个散列表，同时add()负担了大部分的工作。添加一个新类型时，那种类型的Class对象的句柄会被提取出来。随后，利用这个句柄判断容纳了那类对象的一个Vector是否已存在于散列表中。如答案是肯定的，就提取出那个Vector，并将对象加入其中；反之，就将Class对象及新Vector作为一个“键－值”对加入。</p>

  <p>利用keys()，可以得到对所有Class对象的一个“枚举”（Enumeration），而且可用get()，可通过Class对象获取对应的Vector。</p>

  <p>filler()方法非常有趣，因为它利用了ParseTrash.fillBin()的设计――不仅能尝试填充一个Vector，也能用它的addTrash()方法试着填充实现了Fillable（可填充）接口的任何东西。filter()需要做的全部事情就是将一个句柄返回给实现了Fillable的一个接口，然后将这个句柄作为参数传递给fillBin()，就象下面这样：</p>

  <p>ParseTrash.fillBin("Trash.dat", bin.filler());</p>

  <p>为产生这个句柄，我们采用了一个“匿名内部类”（已在第7章讲述）。由于根本不需要用一个已命名的类来实现Fillable，只需要属于那个类的一个对象的句柄即可，所以这里使用匿名内部类是非常恰当的。</p>

  <p>对这个设计，要注意的一个地方是尽管没有设计成对归类加以控制，但在fillBin()每次进行归类的时候，都会将一个Trash对象插入bin。</p>

  <p>通过前面那些例子的学习，DynaTrash类的大多数部分都应当非常熟悉了。这一次，我们不再将新的Trash对象置入类型Vector的一个bin内。由于bin的类型为TypeMap，所以将垃圾（Trash）丢进垃圾筒（Bin）的时候，TypeMap的内部归类机制会立即进行适当的分类。在TypeMap里遍历并对每个独立的Vector进行操作，这是一件相当简单的事情：</p>

  <p>Enumeration keys = bin.keys();</p>

  <p>while(keys.hasMoreElements())</p>

  <p>Trash.sumValue(</p>

  <p>bin.get((Class)keys.nextElement()));</p>

  <p>就象大家看到的那样，新类型向系统的加入根本不会影响到这些代码，亦不会影响TypeMap中的代码。这显然是解决问题最圆满的方案。尽管它确实严重依赖RTTI，但请注意散列表中的每个键－值对都只查找一种类型。除此以外，在我们增加一种新类型的时候，不会陷入“忘记”向系统加入正确代码的尴尬境地，因为根本就没有什么代码需要添加。</p>

  <p>16.9 总结</p>

  <p>从表面看，由于象TrashVisitor.java这样的设计包含了比早期设计数量更多的代码，所以会留下效率不高的印象。试图用各种设计方案达到什么目的应该是我们考虑的重点。设计范式特别适合“将发生变化的东西与保持不变的东西隔离开”。而“发生变化的东西”可以代表许多种变化。之所以发生变化，可能是由于程序进入一个新环境，或者由于当前环境的一些东西发生了变化（例如“用户希望在屏幕上当前显示的图示中添加一种新的几何形状”）。或者就象本章描述的那样，变化可能是对代码主体的不断改进。尽管废品分类以前的例子强调了新型Trash向系统的加入，但TrashVisitor.java允许我们方便地添加新功能，同时不会对Trash结构造成干扰。TrashVisitor.java里确实多出了许多代码，但在Visitor里添加新功能只需要极小的代价。如果经常都要进行此类活动，那么多一些代码也是值得的。</p>

  <p>变化序列的发现并非一件平常事；在程序的初始设计出台以前，那些分析家一般不可能预测到这种变化。除非进入项目设计的后期，否则一些必要的信息是不会显露出来的：有时只有进入设计或最终实现阶段，才能体会到对自己系统一个更深入或更不易察觉需要。添加新类型时（这是“回收”例子最主要的一个重点），可能会意识到只有自己进入维护阶段，而且开始扩充系统时，才需要一个特定的继承结构。</p>

  <p>通过设计范式的学习，大家可体会到最重要的一件事情就是本书一直宣扬的一个观点――多形性是OOP（面向对象程序设计）的全部――已发生了彻底的改变。换句话说，很难“获得”多形性；而一旦获得，就需要尝试将自己的所有设计都造型到一个特定的模子里去。</p>

  <p>设计范式要表明的观点是“OOP并不仅仅同多形性有关”。应当与OOP有关的是“将发生变化的东西同保持不变的东西分隔开来”。多形性是达到这一目的的特别重要的手段。而且假如编程语言直接支持多形性，那么它就显得尤其有用（由于直接支持，所以不必自己动手编写，从而节省大量的精力和时间）。但设计范式向我们揭示的却是达到基本目标的另一些常规途径。而且一旦熟悉并掌握了它的用法，就会发现自己可以做出更有创新性的设计。</p>

  <p>由于《Design Patterns》这本书对程序员造成了如此重要的影响，所以他们纷纷开始寻找其他范式。随着的时间的推移，这类范式必然会越来越多。JimCoplien（http://www.bell-labs.com/~cope主页作者）向我们推荐了这样的一些站点，上面有许多很有价值的范式说明：</p>

  <p>http://st-www.cs.uiuc.edu/users/patterns</p>

  <p>http://c2.com/cgi/wiki</p>

  <p>http://c2.com/ppr</p>

  <p>http://www.bell-labs.com/people/cope/Patterns/Process/index.html</p>

  <p>http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns</p>

  <p>http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic</p>

  <p>http://www.cs.wustl.edu/~schmidt/patterns.html</p>

  <p>http://www.espinc.com/patterns/overview.html</p>

  <p>同时请留意每年都要召开一届权威性的设计范式会议，名为PLOP。会议会出版许多学术论文，第三届已在1997年底召开过了，会议所有资料均由Addison-Wesley出版。</p>

  <p>16.10 练习</p>

  <p>(1) 将SingletonPattern.java作为起点，创建一个类，用它管理自己固定数量的对象。</p>

  <p>(2) 为TrashVisitor.java添加一个名为Plastic（塑料）的类。</p>

  <p>(3) 为DynaTrash.java同样添加一个Plastic（塑料）类。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------第17章 项目</p>

  <p>本章包含了一系列项目，它们都以本书介绍的内容为基础，并对早期的章节进行了一定程度</p>

  <div class="mbppagebreak"></div>
</body>
</html>
